//
// This file contains a 'Sample Driver' and is licensed as such
// under the terms of your license agreement with Intel or your
// vendor.  This file may be modified by the user, subject to
// the additional terms of the license agreement
//
/** @file
  UserAuthentication DXE password wrapper.

  Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>
  This software and associated documentation (if any) is furnished
  under a license and may only be used or copied in accordance
  with the terms of the license. Except as permitted by such
  license, no part of this software or documentation may be
  reproduced, stored in a retrieval system, or transmitted in any
  form or by any means without the express written consent of
  Intel Corporation.

**/

#include "UserAuthenticationDxe.h"

/**
  Initialize the communicate buffer using DataSize and Function.

  @param[out]      DataPtr          Points to the data in the communicate buffer.
  @param[in]       DataSize         The data size to send to SMM.
  @param[in]       Function         The function number to initialize the communicate header.
  @param[in]       UserGuid         The user GUID of the password.

  @retval EFI_INVALID_PARAMETER     The data size is too big.
  @retval EFI_SUCCESS               Find the specified variable.
**/
VOID*
InitCommunicateBuffer (
  OUT     VOID                              **DataPtr OPTIONAL,
  IN      UINTN                             DataSize,
  IN      UINTN                             Function,
  IN      EFI_GUID                          *UserGuid
  )
{
  EFI_SMM_COMMUNICATE_HEADER                *SmmCommunicateHeader;
  SMM_PASSWORD_COMMUNICATE_HEADER           *SmmPasswordFunctionHeader;
  VOID                                      *Buffer;
  EDKII_PI_SMM_COMMUNICATION_REGION_TABLE   *SmmCommRegionTable;
  EFI_MEMORY_DESCRIPTOR                     *SmmCommMemRegion;
  UINTN                                     Index;
  UINTN                                     Size;
  EFI_STATUS                                Status;

  Buffer = NULL;
  Status = EfiGetSystemConfigurationTable (
             &gEdkiiPiSmmCommunicationRegionTableGuid,
             (VOID **) &SmmCommRegionTable
             );
  if (EFI_ERROR (Status)) {
    return NULL;
  }
  ASSERT (SmmCommRegionTable != NULL);
  SmmCommMemRegion = (EFI_MEMORY_DESCRIPTOR *) (SmmCommRegionTable + 1);
  Size = 0;
  for (Index = 0; Index < SmmCommRegionTable->NumberOfEntries; Index++) {
    if (SmmCommMemRegion->Type == EfiConventionalMemory) {
      Size = EFI_PAGES_TO_SIZE ((UINTN) SmmCommMemRegion->NumberOfPages);
      if (Size >= (DataSize + OFFSET_OF (EFI_SMM_COMMUNICATE_HEADER, Data) + sizeof (SMM_PASSWORD_COMMUNICATE_HEADER))) {
        break;
      }
    }
    SmmCommMemRegion = (EFI_MEMORY_DESCRIPTOR *) ((UINT8 *) SmmCommMemRegion + SmmCommRegionTable->DescriptorSize);
  }
  ASSERT (Index < SmmCommRegionTable->NumberOfEntries);

  Buffer = (VOID*)(UINTN)SmmCommMemRegion->PhysicalStart;
  ASSERT (Buffer != NULL);
  SmmCommunicateHeader = (EFI_SMM_COMMUNICATE_HEADER *) Buffer;
  CopyGuid (&SmmCommunicateHeader->HeaderGuid, UserGuid);
  SmmCommunicateHeader->MessageLength = DataSize + sizeof (SMM_PASSWORD_COMMUNICATE_HEADER);

  SmmPasswordFunctionHeader = (SMM_PASSWORD_COMMUNICATE_HEADER *) SmmCommunicateHeader->Data;
  ZeroMem (SmmPasswordFunctionHeader, DataSize + sizeof (SMM_PASSWORD_COMMUNICATE_HEADER));
  SmmPasswordFunctionHeader->Function = Function;
  CopyGuid (&SmmPasswordFunctionHeader->UserGuid, UserGuid);
  if (DataPtr != NULL) {
    *DataPtr = SmmPasswordFunctionHeader + 1;
  }

  return Buffer;
}

/**
  Send the data in communicate buffer to SMM.

  @param[in]   Buffer                 Points to the data in the communicate buffer.
  @param[in]   DataSize               This size of the function header and the data.

  @retval      EFI_SUCCESS            Success is returned from the functin in SMM.
  @retval      Others                 Failure is returned from the function in SMM.

**/
EFI_STATUS
SendCommunicateBuffer (
  IN      VOID                              *Buffer,
  IN      UINTN                             DataSize
  )
{
  EFI_STATUS                                Status;
  UINTN                                     CommSize;
  EFI_SMM_COMMUNICATE_HEADER                *SmmCommunicateHeader;
  SMM_PASSWORD_COMMUNICATE_HEADER           *SmmPasswordFunctionHeader;

  CommSize = DataSize + OFFSET_OF (EFI_SMM_COMMUNICATE_HEADER, Data) + sizeof (SMM_PASSWORD_COMMUNICATE_HEADER);

  Status = mSmmCommunication->Communicate (mSmmCommunication, Buffer, &CommSize);
  ASSERT_EFI_ERROR (Status);

  SmmCommunicateHeader = (EFI_SMM_COMMUNICATE_HEADER *) Buffer;
  SmmPasswordFunctionHeader = (SMM_PASSWORD_COMMUNICATE_HEADER *)SmmCommunicateHeader->Data;
  return  SmmPasswordFunctionHeader->ReturnStatus;
}

/**
  Validata if the password is correct.

  @param[in]  UserGuid               The user GUID of the password.
  @param[in]  Password               The user input password.
  @param[in]  PasswordSize           The size of Password in byte.

  @retval EFI_SUCCESS              The password is correct.
  @retval EFI_SECURITY_VIOLATION   The password is incorrect.
**/
EFI_STATUS
ValidatePassword (
  IN   EFI_GUID     *UserGuid,
  IN   CHAR16       *Password,
  IN   UINTN        PasswordSize
  )
{
  EFI_STATUS                                Status;
  VOID                                      *Buffer;
  SMM_PASSWORD_COMMUNICATE_VERIFY_PASSWORD  *VerifyPassword;

  ASSERT (Password != NULL);

  if (PasswordSize > sizeof(VerifyPassword->Password) * sizeof(CHAR16)) {
    return EFI_INVALID_PARAMETER;
  }

  Buffer = InitCommunicateBuffer (
             (VOID**)&VerifyPassword,
             sizeof(*VerifyPassword),
             SMM_PASSWORD_FUNCTION_VERIFY_PASSWORD,
             UserGuid
             );
  if (Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Status = UnicodeStrToAsciiStrS (Password, VerifyPassword->Password, sizeof(VerifyPassword->Password));
  if (EFI_ERROR(Status)) {
    return Status;
  }

  Status = SendCommunicateBuffer (Buffer, sizeof(*VerifyPassword));
  if (EFI_ERROR (Status)) {
    goto EXIT;
  }

EXIT:
  ZeroMem (VerifyPassword, sizeof(*VerifyPassword));
  return Status;
}

/**
  Set a new password.

  @param[in]  UserGuid               The user GUID of the password.
  @param[in]  NewPassword            The user input new password.
                                     NULL means clear password.
  @param[in]  NewPasswordSize        The size of NewPassword in byte.
  @param[in]  OldPassword            The user input old password.
                                     NULL means no old password.
  @param[in]  OldPasswordSize        The size of OldPassword in byte.

  @retval EFI_SUCCESS              The password is correct.
  @retval EFI_SECURITY_VIOLATION   The password is incorrect.
  @retval EFI_OUT_OF_RESOURCES     Insufficient resources to set the password.
**/
EFI_STATUS
SetPassword (
  IN   EFI_GUID     *UserGuid,
  IN   CHAR16       *NewPassword,     OPTIONAL
  IN   UINTN        NewPasswordSize,
  IN   CHAR16       *OldPassword,     OPTIONAL
  IN   UINTN        OldPasswordSize
  )
{
  EFI_STATUS                                Status;
  VOID                                      *Buffer;
  SMM_PASSWORD_COMMUNICATE_SET_PASSWORD     *SetPassword;

  if (NewPasswordSize > sizeof(SetPassword->NewPassword) * sizeof(CHAR16)) {
    return EFI_INVALID_PARAMETER;
  }
  if (OldPasswordSize > sizeof(SetPassword->OldPassword) * sizeof(CHAR16)) {
    return EFI_INVALID_PARAMETER;
  }

  Buffer = InitCommunicateBuffer (
             (VOID**)&SetPassword,
             sizeof(*SetPassword),
             SMM_PASSWORD_FUNCTION_SET_PASSWORD,
             UserGuid
             );
  if (Buffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  if (NewPassword != NULL) {
    Status = UnicodeStrToAsciiStrS (NewPassword, SetPassword->NewPassword, sizeof(SetPassword->NewPassword));
    if (EFI_ERROR(Status)) {
      return Status;
    }
  } else {
    SetPassword->NewPassword[0] = 0;
  }

  if (OldPassword != NULL) {
    Status = UnicodeStrToAsciiStrS (OldPassword, SetPassword->OldPassword, sizeof(SetPassword->OldPassword));
    if (EFI_ERROR(Status)) {
      return Status;
    }
  } else {
    SetPassword->OldPassword[0] = 0;
  }

  Status = SendCommunicateBuffer (Buffer, sizeof(*SetPassword));
  if (EFI_ERROR (Status)) {
    goto EXIT;
  }

EXIT:
  ZeroMem (SetPassword, sizeof(*SetPassword));
  return Status;
}

/**
  Return if the password is set.

  @param[in]  UserGuid               The user GUID of the password.

  @retval TRUE    The password is set.
  @retval FALSE   The password is not set.
**/
BOOLEAN
IsPasswordSet (
  IN   EFI_GUID     *UserGuid
  )
{
  EFI_STATUS                                Status;
  VOID                                      *Buffer;

  Buffer = InitCommunicateBuffer (
             NULL,
             0,
             SMM_PASSWORD_FUNCTION_IS_PASSWORD_SET,
             UserGuid
             );
  if (Buffer == NULL) {
    return FALSE;
  }

  Status = SendCommunicateBuffer (Buffer, 0);
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  return TRUE;
}

